We now need to calculate a TF-IDF dictionary for each document. This will be a sparse dictionary.

Our first job is to create a tf-idf vector for each document.


In [1]:
import os
from glob import glob
result = [y for x in os.walk("../raw/processed") for y in glob(os.path.join(x[0], '*.txt'))]
print(result[0])


../raw/processed\0103DOM.zip\logFechamento.txt

In [2]:
# remove all files called "logFechamento.txt"
list_of_files = [item for item in result if not item.endswith("logFechamento.txt")]
print(list_of_files[0])


../raw/processed\0103DOM.zip\AACAADM.0\Publicacao\AACAADM.0839.txt

In [3]:
# The text files are recorded with arbitrary encoding, so we need to cater for that.
import io

def read_hostile_text(path_txt):
    encodings = [
        'utf-8',
        'latin_1',
        'utf_16',
        'cp1250',
    ]
    for encoding in encodings:
        file = io.open(path_txt, "r", encoding=encoding)
        try:
            text = file.read()
            file.close()
            return text
        except UnicodeDecodeError:
            file.close()
    print('Could not decode', path_txt)
    return None

In [4]:
text = read_hostile_text(list_of_files[0])
print(text)


010306 Publicacao 


((TITULO))DECRETO  Nº 56.839,  DE  29  DE  FEVEREIRO  DE  2016
((EMENTA))Institui o Comitê Intersecretarial do Circuito das Compras da Cidade de São Paulo – Comitê SP–Circuito das Compras; atribui incumbências à Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo; transfere os cargos de provimento em comissão que especifica.
((TEXTO))FERNANDO HADDAD, Prefeito do Município de São Paulo, no uso das atribuições que lhe são conferidas por lei,
D E C R E T A:
Art. 1º Fica instituído o Comitê Intersecretarial do Circuito das Compras da Cidade de São Paulo – Comitê SP–Circuito das Compras, com a incumbência de realizar o acompanhamento do contrato celebrado entre a Prefeitura do Município de São Paulo, por meio da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo, e o Consórcio Circuito SP, vencedor da Concorrência Pública nº 01-B/SDTE/2014.
Art. 2º O Comitê SP–Circuito das Compras será composto pelos Secretários das seguintes Pastas:
I – Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo, que presidirá o colegiado;
II – Secretaria do Governo Municipal;
III – Secretaria Municipal dos Negócios Jurídicos;
IV – Secretaria Municipal de Finanças e Desenvolvimento Econômico;
V – Secretaria Municipal de Coordenação das Subprefeituras;
VI – Secretaria Municipal de Desenvolvimento Urbano;
VII – Secretaria Municipal de Infraestrutura Urbana e Obras;
VIII – Secretaria Municipal de Habitação;
IX – Secretaria Municipal de Licenciamento;
X – Secretaria Municipal de Transportes.
§ 1º Na impossibilidade de comparecimento às reuniões do colegiado, os titulares dos órgãos referidos no “caput” deste artigo poderão se fazer representar pelos respectivos Secretários Adjuntos ou Chefes de Gabinete.
§ 2º Nos casos de necessidades técnicas específicas de Secretarias Municipais sem representação no Comitê SP–Circuito das Compras, o Presidente do colegiado poderá convidar os respectivos titulares para tratar dos assuntos que lhes sejam afetos.
Art. 3º Caberá à Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo:
I - prover a estrutura necessária à realização dos trabalhos afetos ao Comitê SP–Circuito das Compras;
II - acompanhar a execução, atestar o cumprimento, aplicar sanções e representar o Município de São Paulo no âmbito do Contrato de Concessão de Obra Pública para a Construção, Implantação, Ação, Manutenção e Exploração Econômica do Circuito das Compras no Município de São Paulo;
Parágrafo único. Para a finalidade prevista no inciso I do “caput” deste artigo, poderá o Presidente do colegiado constituir Secretaria Executiva, composta por servidores da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo.
Art. 4º Incumbirá também à Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo representar o Município no Contrato de Cessão sob o Regime de Concessão de Direito Real de Uso Resolúvel em Condições Especiais (CDRU), nos termos do artigo 18, inciso I, da Lei Federal nº 9.636, de 15 de maio de 1998, do imóvel denominado Pátio do Pari, com 119.761,65 m², localizado no Município de São Paulo, Estado de São Paulo, que faz a União à Prefeitura do Município de São Paulo, conforme o processo SPU nº 04977.011351/2011-21.
§ 1º Ficam ressalvadas as obrigações decorrentes da CDRU, de competência das demais Secretarias Municipais, que deverão, no âmbito do Comitê SP–Circuito das Compras, apresentar plano de trabalho e relatório de execução acerca das responsabilidades do Município resultantes do compromisso firmado com a União.
§ 2º É de responsabilidade da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo a criação, em conjunto com a Secretaria do Patrimônio da União, de Comitê Gestor para acompanhamento do desempenho dos valores repassados para a União no contexto da CDRU, assegurada a oitiva e a participação da comunidade interessada. 
§ 3º Para o cumprimento do disposto no § 2º deste artigo, a Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo deverá organizar eleições diretas para a representação dos cadastrados na Lista de Comerciantes a ser entregue pela Prefeitura ao Consórcio Circuito SP, na forma da regulamentação própria. 
Art. 5º Ficam transferidos para o Gabinete do Secretário, da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo, os cargos abaixo discriminados, destinados ao gerenciamento das atividades relacionadas ao Circuito das Compras;
I – do Gabinete do Secretário, da Secretaria Municipal de Coordenação das Subprefeituras;
a) 1 (um) cargo de Assessor Especial, Ref. DAS-15, de livre provimento em comissão;
b) 1 (um) cargo de Auxiliar de Gabinete, Ref. DAI-02, de livre provimento em comissão pelo Prefeito;
II – da Secretaria Municipal de Coordenação das Subprefeituras, 3 (três) cargos de Encarregado de Serviços Gerais, Ref. DAI-02, de livre provimento em comissão.
Art. 6º Este decreto entrará em vigor na data de sua publicação, revogados os Decretos nº 54.296, de 2 de setembro de 2013, e nº 54.318, de 6 de setembro de 2013.
PREFEITURA DO MUNICÍPIO DE SÃO PAULO, aos 29 de fevereiro de 2016, 463º da fundação de São Paulo.
FERNANDO HADDAD, PREFEITO
ARTUR HENRIQUE DA SILVA SANTOS, Secretário Municipal do Desenvolvimento, Trabalho e Empreendedorismo
FRANCISCO MACENA DA SILVA, Secretário do Governo Municipal
Publicado na Secretaria do Governo Municipal, em 29 de fevereiro de 2016.

((TITULO))DECRETO  Nº 56.840,  DE  29  DE  FEVEREIRO  DE  2016
((EMENTA))Declara de utilidade pública a entidade que especifica.
((TEXTO))FERNANDO HADDAD, Prefeito do Município de São Paulo, no uso das atribuições que lhe são conferidas por lei e à vista do que consta do processo administrativo nº 2015-0.298.432-2,
D E C R E T A:
Art. 1º Fica declarada de utilidade pública, nos termos da Lei nº 4.819, de 21 de novembro de 1955, com alterações posteriores, a entidade denominada ASSOCIAÇÃO PAULISTA DE CIRURGIÕES DENTISTAS, CNPJ nº 47.331.822/0001-19, situada no Município de São Paulo.
Art. 2º Este decreto entrará em vigor na data de sua publicação.
PREFEITURA DO MUNICÍPIO DE SÃO PAULO, aos 29 de fevereiro de 2016, 463º da fundação de São Paulo.
FERNANDO HADDAD, PREFEITO
FRANCISCO MACENA DA SILVA, Secretário do Governo Municipal 
Publicado na Secretaria do Governo Municipal, em 29 de fevereiro de 2016.



In [5]:
# For each file in that list, extract the document and put it in the documents list.
documents = []
for filename in list_of_files:
  documents.append(read_hostile_text(filename))

The documents are very poluted, with many numbers, which will end up being interpreted as tokens, where they don't really add any semantic value. So we'll filter those out.


In [65]:
def token_is_valid(token):
    return (not token.isdigit()) and ('__' not in token) and ('((' not in token) and ('\n' not in token)

In [66]:
# This assumes that the separator is a space (' '), which will not always be the case...
# TODO deal with colons, semicolons, \x96, brackets
# TODO deal with the special case where a word is spelled with spaces between each letter (e.g. 'D E C R E T A')
documents = [''.join([token for token in document if token_is_valid(token)]) for document in documents]

In [67]:
documents[0]


Out[67]:
' Publicacao ((TITULO))DECRETO  Nº .,  DE    DE  FEVEREIRO  DE  ((EMENTA))Institui o Comitê Intersecretarial do Circuito das Compras da Cidade de São Paulo \x96 Comitê SP\x96Circuito das Compras; atribui incumbências à Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo; transfere os cargos de provimento em comissão que especifica.((TEXTO))FERNANDO HADDAD, Prefeito do Município de São Paulo, no uso das atribuições que lhe são conferidas por lei,D E C R E T A:Art. º Fica instituído o Comitê Intersecretarial do Circuito das Compras da Cidade de São Paulo \x96 Comitê SP\x96Circuito das Compras, com a incumbência de realizar o acompanhamento do contrato celebrado entre a Prefeitura do Município de São Paulo, por meio da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo, e o Consórcio Circuito SP, vencedor da Concorrência Pública nº -B/SDTE/.Art. º O Comitê SP\x96Circuito das Compras será composto pelos Secretários das seguintes Pastas:I \x96 Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo, que presidirá o colegiado;II \x96 Secretaria do Governo Municipal;III \x96 Secretaria Municipal dos Negócios Jurídicos;IV \x96 Secretaria Municipal de Finanças e Desenvolvimento Econômico;V \x96 Secretaria Municipal de Coordenação das Subprefeituras;VI \x96 Secretaria Municipal de Desenvolvimento Urbano;VII \x96 Secretaria Municipal de Infraestrutura Urbana e Obras;VIII \x96 Secretaria Municipal de Habitação;IX \x96 Secretaria Municipal de Licenciamento;X \x96 Secretaria Municipal de Transportes.§ º Na impossibilidade de comparecimento às reuniões do colegiado, os titulares dos órgãos referidos no \x93caput\x94 deste artigo poderão se fazer representar pelos respectivos Secretários Adjuntos ou Chefes de Gabinete.§ º Nos casos de necessidades técnicas específicas de Secretarias Municipais sem representação no Comitê SP\x96Circuito das Compras, o Presidente do colegiado poderá convidar os respectivos titulares para tratar dos assuntos que lhes sejam afetos.Art. º Caberá à Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo:I - prover a estrutura necessária à realização dos trabalhos afetos ao Comitê SP\x96Circuito das Compras;II - acompanhar a execução, atestar o cumprimento, aplicar sanções e representar o Município de São Paulo no âmbito do Contrato de Concessão de Obra Pública para a Construção, Implantação, Ação, Manutenção e Exploração Econômica do Circuito das Compras no Município de São Paulo;Parágrafo único. Para a finalidade prevista no inciso I do \x93caput\x94 deste artigo, poderá o Presidente do colegiado constituir Secretaria Executiva, composta por servidores da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo.Art. º Incumbirá também à Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo representar o Município no Contrato de Cessão sob o Regime de Concessão de Direito Real de Uso Resolúvel em Condições Especiais (CDRU), nos termos do artigo , inciso I, da Lei Federal nº ., de  de maio de , do imóvel denominado Pátio do Pari, com ., m, localizado no Município de São Paulo, Estado de São Paulo, que faz a União à Prefeitura do Município de São Paulo, conforme o processo SPU nº ./-.§ º Ficam ressalvadas as obrigações decorrentes da CDRU, de competência das demais Secretarias Municipais, que deverão, no âmbito do Comitê SP\x96Circuito das Compras, apresentar plano de trabalho e relatório de execução acerca das responsabilidades do Município resultantes do compromisso firmado com a União.§ º É de responsabilidade da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo a criação, em conjunto com a Secretaria do Patrimônio da União, de Comitê Gestor para acompanhamento do desempenho dos valores repassados para a União no contexto da CDRU, assegurada a oitiva e a participação da comunidade interessada. § º Para o cumprimento do disposto no § º deste artigo, a Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo deverá organizar eleições diretas para a representação dos cadastrados na Lista de Comerciantes a ser entregue pela Prefeitura ao Consórcio Circuito SP, na forma da regulamentação própria. Art. º Ficam transferidos para o Gabinete do Secretário, da Secretaria Municipal do Desenvolvimento, Trabalho e Empreendedorismo, os cargos abaixo discriminados, destinados ao gerenciamento das atividades relacionadas ao Circuito das Compras;I \x96 do Gabinete do Secretário, da Secretaria Municipal de Coordenação das Subprefeituras;a)  (um) cargo de Assessor Especial, Ref. DAS-, de livre provimento em comissão;b)  (um) cargo de Auxiliar de Gabinete, Ref. DAI-, de livre provimento em comissão pelo Prefeito;II \x96 da Secretaria Municipal de Coordenação das Subprefeituras,  (três) cargos de Encarregado de Serviços Gerais, Ref. DAI-, de livre provimento em comissão.Art. º Este decreto entrará em vigor na data de sua publicação, revogados os Decretos nº ., de  de setembro de , e nº ., de  de setembro de .PREFEITURA DO MUNICÍPIO DE SÃO PAULO, aos  de fevereiro de , º da fundação de São Paulo.FERNANDO HADDAD, PREFEITOARTUR HENRIQUE DA SILVA SANTOS, Secretário Municipal do Desenvolvimento, Trabalho e EmpreendedorismoFRANCISCO MACENA DA SILVA, Secretário do Governo MunicipalPublicado na Secretaria do Governo Municipal, em  de fevereiro de .((TITULO))DECRETO  Nº .,  DE    DE  FEVEREIRO  DE  ((EMENTA))Declara de utilidade pública a entidade que especifica.((TEXTO))FERNANDO HADDAD, Prefeito do Município de São Paulo, no uso das atribuições que lhe são conferidas por lei e à vista do que consta do processo administrativo nº -..-,D E C R E T A:Art. º Fica declarada de utilidade pública, nos termos da Lei nº ., de  de novembro de , com alterações posteriores, a entidade denominada ASSOCIAÇÃO PAULISTA DE CIRURGIÕES DENTISTAS, CNPJ nº ../-, situada no Município de São Paulo.Art. º Este decreto entrará em vigor na data de sua publicação.PREFEITURA DO MUNICÍPIO DE SÃO PAULO, aos  de fevereiro de , º da fundação de São Paulo.FERNANDO HADDAD, PREFEITOFRANCISCO MACENA DA SILVA, Secretário do Governo Municipal Publicado na Secretaria do Governo Municipal, em  de fevereiro de .'

In [68]:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(min_df=1)
tfidf = vectorizer.fit_transform(documents)

In [69]:
vectorizer.vocabulary_


Out[69]:
{'dellai': 97553,
 'horassolange': 172313,
 'confirmadolook': 79820,
 'picinini': 277022,
 'acasalamento': 2648,
 'mdbr': 229321,
 'suplentemarcileia': 345041,
 'smrgfábio': 333011,
 'espontao': 127420,
 'marniê': 225161,
 'vasconcelospara': 368490,
 'spartaco': 338648,
 'perissini': 274478,
 'limpocomunicado': 212730,
 'leonor': 208932,
 'restritivos': 304154,
 'nortecristina': 251855,
 'passeiorenato': 268213,
 'lestevania': 209701,
 'chiulli': 65816,
 'autorizaçãoespacho': 31888,
 'atsi': 30150,
 'annapurna': 18621,
 'organizadosjustificativa': 260204,
 'suarella': 341656,
 'huletia': 174602,
 'abandonoprocessos': 1131,
 'danfercoronel': 93529,
 'zafolon': 380670,
 'panese': 264656,
 'caxe': 60539,
 'piritubaeunice': 278504,
 'pondorf': 281973,
 'racialluiz': 294572,
 'relatorial': 301234,
 'glecia': 156647,
 'menorcnpj': 231822,
 'kunz': 203927,
 'comag': 75863,
 'tinentes': 354541,
 'coletorakoto': 74100,
 'anjosnº': 18579,
 'slq': 329925,
 'apost': 21339,
 'bombardas': 44287,
 'pisetta': 278710,
 'guaciara': 160693,
 'amaroitem': 14936,
 'frontin': 146371,
 'emenda': 119102,
 'brbna': 47812,
 'jaquely': 192998,
 'rogelli': 308480,
 'hóbras': 175163,
 'hspmangela': 174115,
 'mascotea': 226223,
 'zoonoseshorário': 382643,
 'approbatto': 21474,
 'entoados': 122975,
 'berulis': 40344,
 'labzelizabete': 204673,
 'íamos': 384373,
 'kabin': 199090,
 'julierme': 197458,
 'fotopoulus': 144212,
 'numerais': 253757,
 'eais': 112620,
 'banjamina': 35263,
 'horasclarisse': 169399,
 'ips': 187450,
 'maciberg': 219503,
 'exercíco': 132667,
 'telefones': 351399,
 'oeting': 256586,
 'sarazate': 316332,
 'tassiany': 349609,
 'valdevique': 366912,
 'biriba': 42415,
 'asecoresolve': 27493,
 'dvanaide': 112111,
 'sfmspdanilo': 324708,
 'oscalices': 261224,
 'vagasubo': 366548,
 'fariamaria': 135688,
 'emocionaram': 119595,
 'dilse': 105024,
 'poló': 281818,
 'ardem': 24716,
 'estarlich': 128188,
 'zenardi': 381770,
 'schlogel': 318839,
 'jovanete': 196287,
 'reeditado': 299026,
 'bendilate': 38976,
 'infindo': 181994,
 'perf': 274113,
 'rocchetto': 307752,
 'urbanística': 365322,
 'hspmsuzi': 174248,
 'zangirolami': 381149,
 'tritsis': 360558,
 'dynamykha': 112312,
 'telecópicos': 351368,
 'hirata': 167411,
 'degref': 96788,
 'comprovações': 77673,
 'horascronograma': 169521,
 'vanzeto': 368135,
 'zanfra': 381132,
 'elevará': 117353,
 'fraguglia': 144613,
 'classificaçãoem': 69231,
 'algodoeiro': 12320,
 'uélita': 366375,
 'autorizariam': 31847,
 'saneamentos': 314821,
 'hubert': 174516,
 'jingpeng': 194652,
 'conci': 78608,
 'domari': 108947,
 'yury': 380449,
 'saroa': 316477,
 'smscamila': 333185,
 'carlosrua': 57513,
 'capinação': 55989,
 'baixatomas': 34341,
 'dadosquantidade': 92620,
 'aloísio': 13388,
 'hipodermicas': 167287,
 'eutimio': 130135,
 'frizz': 146257,
 'noturnoedmundo': 252573,
 'ndominio': 247412,
 'tunin': 361810,
 'omissãoférias': 258526,
 'referendum': 299334,
 'elasticidade': 116484,
 'mariolaine': 224767,
 'pontuaçãoscipopulis': 282227,
 'sentei': 322526,
 'nierton': 249869,
 'nabda': 244978,
 'transversas': 359029,
 'arcibelliavenida': 24629,
 'ihco': 177091,
 'atenham': 29282,
 'horasnayane': 171705,
 'extrassuk': 133785,
 'pastosa': 268619,
 'infr': 182362,
 'beatrizcolégio': 37832,
 'eir': 116014,
 'higienizarem': 166929,
 'saparecida': 315970,
 'coemrcial': 71860,
 'enviavam': 123374,
 'nutripot': 254031,
 'smduildete': 331416,
 'frztotal': 146624,
 'fonográfico': 143009,
 'mdmtitular': 229358,
 'iapl': 175438,
 'negociaçao': 247828,
 'educadoresauditório': 114792,
 'bentoenquadramento': 39443,
 'horashelia': 170484,
 'domelio': 108967,
 'noschese': 252222,
 'potássio': 283411,
 'nasccimento': 246306,
 'shubart': 326086,
 'mzvihzuqeictw': 243988,
 'meireslaine': 230577,
 'deliberarem': 97390,
 'vinhato': 372527,
 'coletoradireitos': 73379,
 'lapadarci': 206085,
 'milly': 235088,
 'martí': 225866,
 'kumao': 203855,
 'itinerantetendo': 190310,
 'seremos': 322917,
 'ahmrosemeire': 9568,
 'oms': 258632,
 'teramitsu': 352275,
 'migliati': 234488,
 'financeiras': 140280,
 'hiraide': 167383,
 'portados': 282630,
 'funilandia': 147793,
 'rithiely': 307099,
 'landim': 205797,
 'refletir': 299474,
 'eminente': 119378,
 'horasedmarlos': 169805,
 'criou': 88640,
 'aranhaalça': 23986,
 'phbdsgw': 276519,
 'emis': 119398,
 'mazloum': 228742,
 'prorrogado': 288829,
 'weil': 376579,
 'inesequivel': 181689,
 'dreon': 110720,
 'mochetti': 237529,
 'substanciais': 342413,
 'jy': 198926,
 'drica': 110785,
 'direitokaroline': 105823,
 'presidentedenise': 285459,
 'enucleação': 123225,
 'migrados': 234526,
 'escova': 126122,
 'melosuplente': 231124,
 'lvk': 218570,
 'esuesu': 129223,
 'outerelo': 262164,
 'aprovadoºendocrinologiadébora': 22317,
 'ssado': 339891,
 'mkse': 237037,
 'fendeelcci': 137728,
 'prushitaro': 289824,
 'rudson': 311145,
 'hovenias': 173516,
 'ltdamanutenção': 216542,
 'paulosra': 270011,
 'alq': 13460,
 'cxxii': 91879,
 'instituiçãoaqui': 183877,
 'cmaicgm': 70818,
 'fugihara': 146899,
 'genpro': 152572,
 'takisava': 348352,
 'tva': 362240,
 'antropológico': 19640,
 'risadaria': 306909,
 'fainer': 134792,
 'shigemasa': 325567,
 'aítos': 33563,
 'simulaçâo': 328142,
 'aecotur': 7038,
 'bisnagas': 42553,
 'metrônomo': 233368,
 'malfenas': 221279,
 'possamai': 283014,
 'arcine': 24640,
 'sehabroberval': 320846,
 'chizuko': 65841,
 'semelhante': 321864,
 'conceicaoa': 78355,
 'maratti': 223331,
 'olhido': 257694,
 'estevez': 128519,
 'curta': 91234,
 'prolopafabricante': 288178,
 'orduña': 260052,
 'sirotto': 328957,
 'nazil': 247138,
 'opalaøs': 259252,
 'oficineiros': 256866,
 'diurnoclaudio': 107121,
 'quilom': 293766,
 'passeior': 268197,
 'zituo': 382400,
 'queiros': 293379,
 'rodriguesluizete': 308301,
 'anhxaw': 18355,
 'coro': 85208,
 'senaemei': 322216,
 'reginato': 299884,
 'alienantes': 12480,
 'argentao': 24992,
 'mellos': 231011,
 'valverdeexumação': 367747,
 'contribuiçãofernanda': 83316,
 'confiançaart': 79511,
 'parceladopedro': 265833,
 'anolidiane': 18715,
 'holdingbras': 168326,
 'secaold': 319855,
 'hideo': 166590,
 'suplentearnaldo': 344821,
 'regulamentar': 300399,
 'relacionadasdistritos': 301101,
 'horascadija': 169270,
 'maegava': 220015,
 'rastelerg': 296119,
 'veve': 370812,
 'sabespcesar': 312014,
 'emergênciaocorrência': 119201,
 'guaianasesadiantamentoadiantamento': 160769,
 'minos': 235715,
 'gqd': 158607,
 'centroemilio': 62489,
 'mariluemef': 224588,
 'resigni': 303375,
 'cameras': 54066,
 'rodriguesalbert': 308235,
 'ipmaria': 187202,
 'meurer': 233431,
 'stefanir': 340514,
 'hill': 167082,
 'escolarfica': 125934,
 'triade': 360075,
 'vhmhqyjd': 370937,
 'coletorapensilvânia': 74636,
 'retornado': 304584,
 'desejável': 100691,
 'sociali': 335934,
 'reabilitaçãoend': 296728,
 'turismo': 361985,
 'eppcópia': 123920,
 'fazer': 136610,
 'zumerli': 383035,
 'kwasinski': 204196,
 'ercicio': 124701,
 'válida': 375248,
 'fortilus': 143963,
 'ahmantonio': 9289,
 'contr': 82703,
 'elidi': 117518,
 'teatrla': 350364,
 'mugi': 242012,
 'provocado': 289521,
 'isalix': 188646,
 'usufrui': 365897,
 'managementmadrigal': 221716,
 'colettir': 75327,
 'ratificaçãodo': 296225,
 'cuinha': 90469,
 'tibiriçá': 354145,
 'valdyr': 367048,
 'squeeze': 339757,
 'microterritório': 234330,
 'bluatx': 43266,
 'juventudesmc': 198832,
 'aiod': 9898,
 'padrimonio': 263334,
 'camunha': 54636,
 'pçconector': 291914,
 'lazinhochefe': 207346,
 'camaleõesnome': 53773,
 'gtrhelisangela': 160659,
 'fiamfaamthalia': 139115,
 'ahmcélia': 9330,
 'iqdoris': 187640,
 'irm': 188274,
 'gvalor': 162731,
 'tarses': 349506,
 'dragghianti': 110535,
 'sulmembros': 343969,
 'areend': 24818,
 'inviabilizando': 186298,
 'etoga': 129552,
 'varriçãopara': 368416,
 'asc': 27365,
 'renam': 301866,
 'classificaçãoabílio': 69211,
 'bucalem': 49774,
 'banananotas': 35009,
 'lanzo': 205993,
 'virão': 372956,
 'guaiuvira': 161027,
 'loan': 214315,
 'sukiya': 343607,
 'humanizar': 174663,
 'abuelitas': 2347,
 'hidroconsult': 166665,
 'magderose': 220265,
 'daocorrência': 93843,
 'smadstatiane': 330513,
 'fracassaram': 144476,
 'penha': 272991,
 'funcionalmarcos': 147415,
 'ltdaepppropostas': 216396,
 'gerallista': 153328,
 'jallol': 192379,
 'passeiofelipe': 267810,
 'togeiro': 355998,
 'smsuildete': 334549,
 'rsestabelecimento': 310709,
 'soaes': 335408,
 'judkowitch': 197173,
 'apza': 23443,
 'perifa': 274345,
 'horaseliseuda': 169928,
 'caruy': 58542,
 'fariadeclaração': 135670,
 'gate': 151190,
 'públicaremoçãodocumento': 292150,
 'escoas': 125802,
 'presidiários': 285561,
 'fulcher': 147090,
 'gressos': 159883,
 'hahxgqk': 163432,
 'feio': 137263,
 'veadores': 368844,
 'dubalaco': 111373,
 'designadoapenas': 101172,
 'fetzer': 138774,
 'erk': 125048,
 'concordaram': 78790,
 'hhafosé': 166443,
 'extinguem': 133641,
 'cherto': 65213,
 'entregáveis': 123086,
 'americajoao': 15635,
 'aterado': 29350,
 'escolho': 126020,
 'mundialb': 242632,
 'thielly': 353852,
 'brenelli': 48018,
 'leodir': 208804,
 'terriaca': 352859,
 'eurizania': 130013,
 'amu': 16380,
 'proporcionalmente': 288581,
 'nagamineav': 245337,
 'evalto': 130219,
 'kimako': 201688,
 'bortolleto': 45883,
 'miados': 233886,
 'topdata': 356752,
 'covisamariângela': 87028,
 'horasabilio': 168858,
 'herpes': 166086,
 'kadawaki': 199126,
 'prequero': 285167,
 'adria': 6568,
 'smsugisela': 334527,
 'bogdon': 43848,
 'fepac': 137826,
 'manhorelo': 222235,
 'pellizzon': 272609,
 'regulariacao': 300444,
 'faiaav': 134759,
 'vmdata': 374062,
 'durb': 111904,
 'miyakawa': 236842,
 'coletoraanália': 72712,
 'scordamai': 319280,
 'ostensivo': 261536,
 'cruciais': 89422,
 'girandi': 155957,
 'dissertativatítulos': 106732,
 'exposicao': 133340,
 'ginasta': 155555,
 'californiaemef': 53378,
 'líberoav': 218901,
 'horasrodine': 172076,
 'peraro': 273606,
 'compuadd': 77694,
 'yagima': 379239,
 'goldsztejn': 157501,
 'anghinetti': 18127,
 'oxidegradáveis': 262669,
 'enwtotal': 123458,
 'janovitch': 192826,
 'daros': 94118,
 'contaremos': 82311,
 'propostaslicitante': 288663,
 'boucinha': 46300,
 'sghr': 325052,
 'evidenciem': 130617,
 'cesari': 63279,
 'passeioarceu': 267554,
 'kamfonas': 199557,
 'malandros': 221152,
 'tiquatirareq': 354710,
 'chagasmaria': 64253,
 'naymayer': 247080,
 'odk': 256126,
 'cachoeirinharoberto': 52006,
 'cappelen': 56186,
 'descriminado': 100488,
 'calabrez': 52987,
 'adeodata': 4976,
 'rediscussão': 298849,
 'mariquita': 224803,
 'caldê': 53251,
 'hye': 175011,
 'veraildes': 369790,
 'engfiscal': 122244,
 'secretariado': 320023,
 'rola': 308599,
 'makia': 221023,
 'inocencia': 183098,
 'lanzarottidata': 205968,
 'dispharmadistribuidora': 106498,
 'marivaldo': 224914,
 'hélio': 175124,
 'guaianazesmaria': 160930,
 'surgida': 345577,
 'smspmarco': 334000,
 'lotaca': 215474,
 'médiorenan': 244667,
 'dariam': 94033,
 'executivocria': 132456,
 'metodistajosé': 233200,
 'horasyan': 172812,
 'coletorasaraiva': 74910,
 'satulnino': 316899,
 'descentralizada': 100090,
 'kremer': 203455,
 'puccio': 291118,
 'gaião': 149465,
 'oferecidas': 256668,
 'secadouros': 319831,
 'cardume': 57050,
 'tryo': 361042,
 'dpmar': 110428,
 'uspnathallya': 365839,
 'editinha': 114321,
 'gonzagapcg': 157867,
 'exner': 132887,
 'mllances': 237106,
 'jzwjzw': 198981,
 'schafe': 318511,
 'inexplorados': 181808,
 'elisabete': 117737,
 'asfaixas': 27540,
 'stenke': 340679,
 'cpfmarcela': 87466,
 'teixaira': 351137,
 'otadamente': 261678,
 'pessini': 275664,
 'lislg': 213604,
 'akiko': 10442,
 'aricanduvafabio': 25200,
 'psileni': 290443,
 'begliomini': 38098,
 'construcaoes': 81768,
 'ausenciade': 31104,
 'diversamente': 107477,
 'fragali': 144543,
 'yoshioca': 380137,
 'stam': 340157,
 'bastieri': 36971,
 'desimone': 101224,
 'opriedade': 259604,
 'sejadificultada': 321054,
 'condutores': 79250,
 'arake': 23917,
 'saavedra': 311872,
 'microempresaconsiderando': 234214,
 'lesterodolfo': 209613,
 'eppembalagem': 123939,
 'bertuletti': 40323,
 'gaicher': 149400,
 'vinagredescrição': 372400,
 'smsulia': 334602,
 'duraes': 111834,
 'temdescricao': 351744,
 'tnw': 355754,
 'horascafé': 169272,
 'marianofiscal': 224306,
 'horasjanna': 170763,
 'burai': 50282,
 'alvesanalista': 14178,
 'mellore': 231006,
 'opinei': 259475,
 'sesto': 323976,
 'folloni': 142852,
 'análiserecusa': 19970,
 'alocadora': 13280,
 'depositária': 99030,
 'kokado': 202790,
 'coriacea': 85103,
 'jézio': 199014,
 'sintético': 328703,
 'contestavam': 82515,
 'piracatingaconservação': 278233,
 'infantis': 181893,
 'valceni': 366720,
 'malovini': 221415,
 'aprovadoººneurocirurgiadanilo': 23172,
 'lohanna': 214857,
 'ângelo': 384172,
 'bragan': 46695,
 'saúdedetentorawebmed': 317494,
 'deborah': 95244,
 'amaralemef': 14736,
 'racosta': 294746,
 'alturaapresentação': 13878,
 'framacêutica': 144654,
 'luisav': 217739,
 'shilling': 325658,
 'noturnorafael': 252715,
 'nomeadriana': 251276,
 'aavrichir': 979,
 'svmamario': 346439,
 'moribundo': 239852,
 'implante': 178984,
 'redó': 299020,
 'pompilio': 281896,
 'thomsonae': 353977,
 'lunny': 218036,
 'piazolla': 276856,
 'siguenori': 326723,
 'suljair': 343880,
 'coletoracapuava': 73076,
 'karatêliga': 199891,
 'borger': 45581,
 'denotarmos': 98562,
 'laboratóriohorário': 204580,
 'visões': 373375,
 'blk': 43176,
 'inversao': 186161,
 'coletoralemos': 74134,
 'agticlaudio': 8973,
 'coletoracavas': 73133,
 'exercitam': 132625,
 'planejará': 279580,
 'físicarf': 148734,
 'brunelleschi': 49164,
 'pérgamo': 291951,
 'raix': 295331,
 'especializadoeducação': 126926,
 'setra': 324125,
 'maiorn': 220818,
 'cirurgico': 67936,
 'gavenas': 151352,
 'gassipos': 151057,
 'rfeliana': 305473,
 'locabem': 214402,
 'melanezi': 230707,
 'reativar': 297176,
 'fascista': 136158,
 'documentados': 108518,
 'cairolli': 52823,
 'resultem': 304215,
 'zampirollo': 380979,
 'limpado': 211818,
 'dodaro': 108668,
 'hypertop': 175041,
 'chevitarese': 65257,
 'junhio': 197629,
 'serrador': 323184,
 'montebello': 238911,
 'excelentemarcus': 131777,
 'bachiegga': 33799,
 'magalhãeson': 220201,
 'saic': 312796,
 'plenáriaextraordinária': 280019,
 'cosupel': 86433,
 'efarma': 115265,
 'homologaram': 168560,
 'lanari': 205651,
 'horascreusa': 169499,
 'cegalini': 61529,
 'belicuas': 38445,
 'guiduci': 161933,
 'eiarrima': 115859,
 'machadorequisição': 219422,
 'verioca': 370217,
 'attari': 30176,
 'pauloem': 269777,
 'lestras': 209734,
 'tange': 348962,
 'seemund': 320359,
 'pretendidaa': 285838,
 'agrellir': 8742,
 'braulia': 47672,
 'gaseificados': 150908,
 'passosfestejos': 268489,
 'kasbar': 200105,
 'eliminaçâo': 117648,
 'cazado': 60604,
 'garbulho': 150488,
 'incios': 180175,
 'mayrink': 228629,
 'devivian': 103206,
 'casseli': 59190,
 'pll': 280095,
 'secretariaabnelma': 320007,
 'coletorasecretaria': 74926,
 'unidadeagnaldo': 364137,
 'niicola': 249943,
 'eirelic': 116045,
 'caótica': 60721,
 'letppakz': 209788,
 'cartomante': 58456,
 'castropresidente': 59583,
 'sidtss': 326450,
 'alcelino': 11257,
 'comorazao': 76704,
 'maienovitckr': 220651,
 'fiorezi': 140644,
 'cenelat': 62159,
 'pmpo': 280417,
 'leio': 208240,
 'revisoa': 305138,
 'renery': 302005,
 'agregados': 8724,
 'titularivaldina': 355253,
 'recursonúmero': 298615,
 'luckspuma': 217424,
 'tait': 348120,
 'diversa': 107476,
 'aticamente': 29505,
 'franciely': 144825,
 'mesmomario': 232827,
 'gusi': 162577,
 'apeprem': 20618,
 'lbhxbya': 207436,
 'anastacios': 16829,
 'conformetermo': 80183,
 'mateusrodnei': 227464,
 'nisfestações': 250329,
 'ziliotto': 382202,
 'notifica': 252426,
 'ozolins': 262863,
 'acacia': 2466,
 'santosalex': 315489,
 'soarespontos': 335484,
 'contarolando': 82316,
 'smsucesar': 334400,
 'médicagabriela': 244251,
 'gleizer': 156723,
 'ragnar': 295145,
 'consumidos': 82115,
 'replanilhamento': 302448,
 'surpreende': 345610,
 'oestejairo': 256441,
 'mbleila': 229084,
 'apontamos': 21182,
 'babina': 33632,
 'famigeradas': 135247,
 'sightseeing': 326588,
 'presidenteart': 285435,
 'administrativoart': 5959,
 'ggláucia': 154499,
 'convergir': 83707,
 'passeioedinei': 267727,
 'aice': 9697,
 'gavaldao': 151335,
 'retificando': 304375,
 'manfra': 222081,
 'potêncialmente': 283418,
 'marisalva': 224816,
 'inscriçãoficha': 183425,
 'proibi': 287928,
 'laureane': 206907,
 'contráriopr': 83493,
 'cczmaria': 61026,
 'comai': 75866,
 'numerá': 253796,
 'riossócio': 306844,
 'brancodescrição': 46920,
 'eliakyn': 117420,
 'ensaiavam': 122662,
 'epppimenta': 124069,
 'policromada': 281390,
 'captações': 56308,
 'yasmim': 379539,
 'atravésde': 29970,
 'presenteou': 285328,
 'jiacometti': 194536,
 'drogalife': 110879,
 'mbio': 229053,
 'decondutores': 95698,
 'tattojonas': 349747,
 'nirfabiana': 250278,
 'demariz': 97912,
 'maruca': 225875,
 'viicoordenadoria': 371940,
 'ferreiraprofessor': 138484,
 'necessariasquanto': 247518,
 'tassyana': 349622,
 'velasquez': 369131,
 'áreasconforme': 384043,
 'megaeventos': 230318,
 'pesando': 275501,
 'candidoenquadramento': 55052,
 'rauccirua': 296295,
 'ipplinio': 187277,
 'temborio': 351741,
 'comrian': 77804,
 'carrah': 58059,
 'remuneradoprazo': 301805,
 'ezz': 134089,
 'glediane': 156655,
 'eppcolchete': 123898,
 'ermat': 125099,
 'limas': 211622,
 'encmainhar': 120906,
 'gestãojoel': 154231,
 'regules': 300554,
 'baciano': 33812,
 'norterita': 252104,
 'pactuamos': 263204,
 'dalben': 92998,
 'kariny': 199958,
 'korablikovas': 203019,
 'dored': 109646,
 'fraudulentos': 145459,
 'santinon': 315399,
 'auccavsir': 30499,
 'aldemiro': 11482,
 'cgmkelly': 63996,
 'redkowiez': 298877,
 'selsupervisão': 321574,
 'kutani': 204084,
 'korosi': 203066,
 'czli': 92055,
 'coa': 71266,
 'covisawladimir': 87145,
 'christodio': 66128,
 'excelenteadelson': 131045,
 'nfl': 249337,
 'recessão': 297637,
 'tsukasa': 361238,
 'christofer': 66133,
 'desap': 99719,
 'aresidência': 24914,
 'desobstrucao': 101721,
 'quilombo': 293771,
 'ptbdavid': 290717,
 'raspada': 296084,
 'ganges': 150237,
 'berezowski': 39672,
 'aihzeserá': 9776,
 'sitimo': 329174,
 'anabel': 16496,
 'brasiliensis': 47356,
 'natalinisandra': 246647,
 'avram': 32777,
 'acreditasse': 3886,
 'reunidasdata': 304809,
 'rebouçassala': 297363,
 'nanique': 245915,
 'monumento': 239183,
 'infantilpúblico': 181875,
 'funghi': 147780,
 'odracil': 256276,
 'guanandi': 161104,
 'marçalr': 226050,
 'smspvera': 334186,
 'recreaçãoroseli': 298421,
 'takaishi': 348200,
 'orlaney': 260571,
 'bahcivanji': 34158,
 'doada': 108218,
 'oop': 259216,
 'spctlicitacao': 338749,
 'magenta': 220280,
 'matsuka': 227864,
 'daterra': 94490,
 'sacomãinclusãoflavia': 312349,
 'bettim': 40577,
 'peticionamento': 275938,
 'cgmjose': 63989,
 'cerini': 62888,
 'incansavelmente': 180026,
 'depatamento': 98874,
 'escolher': 126001,
 'uknico': 363422,
 'fragmentadas': 144581,
 'olivé': 258227,
 'aranega': 23977,
 'piassentini': 276813,
 'espectr': 127069,
 'dailde': 92861,
 'nazário': 247166,
 'titularveleda': 355493,
 'indicadorpropostas': 181227,
 'instítuida': 184085,
 'board': 43525,
 'villaverde': 372302,
 'scheck': 318574,
 'capoia': 56145,
 'shinotsuka': 325779,
 'magicamente': 220314,
 'cachoeirinhakarina': 51972,
 'municipalexercício': 242834,
 'geg': 151975,
 'espalhe': 126737,
 'siquiplas': 328852,
 'azadim': 33128,
 'estropiado': 128961,
 'wilka': 377252,
 'frkfrw': 146272,
 'acf': 3143,
 'imaging': 178145,
 'saúdejuliana': 317609,
 'lajuliana': 205369,
 'reparte': 302300,
 'carvalhodos': 58609,
 'smswelington': 334880,
 'javaleria': 193547,
 'narcizo': 246128,
 'jarádanilo': 193419,
 'valombrosana': 367488,
 'cammann': 54234,
 'trovo': 360889,
 'sacab': 312181,
 'improve': 179376,
 'rinopolis': 306784,
 'laboratório': 204569,
 'microbiana': 234133,
 'idealismo': 176187,
 'guaianazesjussara': 160922,
 'pinh': 277672,
 'errodespachos': 125316,
 'detalhará': 102584,
 'incentivou': 180096,
 'flamini': 141534,
 'frenteminima': 145837,
 'oitenata': 257256,
 'biperideeno': 42365,
 'cristinaeugenio': 88885,
 'dizfátima': 107801,
 'soment': 337413,
 'bolota': 44147,
 'berseba': 40078,
 'veronico': 370354,
 'climaites': 70173,
 'ipirangaadriano': 186944,
 'saibel': 312794,
 'frange': 145107,
 'medianiz': 229836,
 'cobres': 71511,
 'mendoza': 231559,
 'pessoasassistente': 275750,
 'geraldode': 153170,
 'oesterose': 256538,
 'nirta': 250311,
 'fxttotal': 148464,
 'nicoleti': 249732,
 'raça': 296532,
 'suldaniela': 343719,
 'hannicilia': 163924,
 'ovou': 262570,
 'horasmuller': 171641,
 'edgle': 113728,
 'diurnorenilce': 107304,
 'nerone': 248606,
 'idoneidade': 176506,
 'marinne': 224735,
 'courbelly': 86686,
 'condomijnio': 79145,
 'graduaçãocomprovante': 158813,
 'concedente': 78321,
 'lanzas': 205974,
 'frutamixfabricante': 146521,
 'prolatar': 288130,
 'armários': 25809,
 'previas': 286011,
 'ribeironivel': 306144,
 'costuradase': 86419,
 'frausto': 145466,
 'coletoraiv': 73954,
 'nakasaki': 245660,
 'alozem': 13385,
 'disaster': 106096,
 'encostada': 121012,
 'araruna': 24109,
 'pichitelli': 277004,
 'smsildete': 333447,
 'spartak': 338649,
 'sepultadornome': 322719,
 'agenia': 8191,
 'efetive': 115365,
 'racheti': 294390,
 'guterman': 162637,
 'namim': 245832,
 'hhobnz': 166481,
 'runner': 311366,
 'ritermo': 307095,
 'inexistirem': 181792,
 'caceia': 51852,
 'partsemsqldesatender': 267020,
 'seseguro': 323816,
 'iglu': 176910,
 'vannucchi': 368062,
 'smadsdébora': 330117,
 'matsushita': 227906,
 'artísticasrony': 27114,
 'titularelma': 355165,
 'barrosoendro': 36421,
 'magelo': 220274,
 'resisencias': 303405,
 'unicsulgabriel': 364100,
 'virtudescca': 372938,
 'cleovania': 69921,
 'kurik': 203988,
 'avarias': 32328,
 'explanasse': 133166,
 'eletrônicoa': 117256,
 'recupere': 298547,
 'passeiovilma': 268374,
 'falcãotranslado': 135017,
 'aravechia': 24235,
 'anhang': 18253,
 'geoespaciais': 152681,
 'sobhi': 335547,
 'mauriciomartins': 228241,
 'alizon': 12773,
 'mpjane': 241340,
 'cascino': 58951,
 ...}

Now we define the building blocks of the k-means algorithm.


In [70]:
import numpy as np

def get_initial_centroids(data, k, seed=None):
    '''Randomly choose k data points as initial centroids'''
    if seed is not None: # useful for obtaining consistent results
        np.random.seed(seed)
    n = data.shape[0] # number of data points
        
    # Pick K indices from range [0, N).
    rand_indices = np.random.randint(0, n, k)
    
    # Keep centroids as dense format, as many entries will be nonzero due to averaging.
    # As long as at least one document in a cluster contains a word,
    # it will carry a nonzero weight in the TF-IDF vector of the centroid.
    centroids = data[rand_indices,:].toarray()
    
    return centroids

After initialization, the k-means algorithm iterates between the following two steps:

  1. Assign each data point to the closest centroid. $$ z_i \gets \mathrm{argmin}_j \|\mu_j - \mathbf{x}_i\|^2 $$
  2. Revise centroids as the mean of the assigned data points. $$ \mu_j \gets \frac{1}{n_j}\sum_{i:z_i=j} \mathbf{x}_i $$

In [71]:
from sklearn.metrics import pairwise_distances

def assign_clusters(data, centroids):
    
    # Compute distances between each data point and the set of centroids:
    distances_from_centroids = pairwise_distances(data, centroids, metric='euclidean')
    
    # Compute cluster assignments for each data point:
    cluster_assignment = np.argmin(distances_from_centroids, axis=1)
    
    return cluster_assignment

In [72]:
def revise_centroids(data, k, cluster_assignment):
    new_centroids = []
    for i in range(k):
        # Select all data points that belong to cluster i. Fill in the blank (RHS only)
        member_data_points = data[cluster_assignment==i,:]
        # Compute the mean of the data points. Fill in the blank (RHS only)
        centroid = member_data_points.mean(axis=0)
        
        # Convert numpy.matrix type to numpy.ndarray type
        centroid = centroid.A1
        new_centroids.append(centroid)
    new_centroids = np.array(new_centroids)
    
    return new_centroids

We need to assess that the k-means algorithm is converging. We can look at the cluster assignments and see if they stabilize over time. In fact, we'll be running the algorithm until the cluster assignments stop changing at all. To be extra safe, and to assess the clustering performance, we'll be looking at an additional criteria: the sum of all squared distances between data points and centroids. This is defined as $$ J(\mathcal{Z},\mu) = \sum_{j=1}^k \sum_{i:z_i = j} \|\mathbf{x}_i - \mu_j\|^2. $$ The smaller the distances, the more homogeneous the clusters are. In other words, we'd like to have "tight" clusters.


In [73]:
def compute_heterogeneity(data, k, centroids, cluster_assignment):
    
    heterogeneity = 0.0
    for i in range(k):
        
        # Select all data points that belong to cluster i. Fill in the blank (RHS only)
        member_data_points = data[cluster_assignment==i, :]
        
        if member_data_points.shape[0] > 0: # check if i-th cluster is non-empty
            # Compute distances from centroid to data points (RHS only)
            distances = pairwise_distances(member_data_points, [centroids[i]], metric='euclidean')
            squared_distances = distances**2
            heterogeneity += np.sum(squared_distances)
        
    return heterogeneity

In [74]:
def kmeans(data, k, initial_centroids, maxiter, record_heterogeneity=None, verbose=False):
    '''This function runs k-means on given data and initial set of centroids.
       maxiter: maximum number of iterations to run.
       record_heterogeneity: (optional) a list, to store the history of heterogeneity as function of iterations
                             if None, do not store the history.
       verbose: if True, print how many data points changed their cluster labels in each iteration'''
    centroids = initial_centroids[:]
    prev_cluster_assignment = None
    
    for itr in range(maxiter):        
        if verbose:
            print(itr)
        
        # 1. Make cluster assignments using nearest centroids
        cluster_assignment = assign_clusters(data, centroids)
            
        # 2. Compute a new centroid for each of the k clusters, averaging all data points assigned to that cluster.
        centroids = revise_centroids(data, k, cluster_assignment)
            
        # Check for convergence: if none of the assignments changed, stop
        if prev_cluster_assignment is not None and \
          (prev_cluster_assignment==cluster_assignment).all():
            break
        
        # Print number of new assignments 
        if prev_cluster_assignment is not None:
            num_changed = np.sum(prev_cluster_assignment!=cluster_assignment)
            if verbose:
                print('    {0:5d} elements changed their cluster assignment.'.format(num_changed))   
        
        # Record heterogeneity convergence metric
        if record_heterogeneity is not None:
            score = compute_heterogeneity(data, k, centroids, cluster_assignment)
            record_heterogeneity.append(score)
        
        prev_cluster_assignment = cluster_assignment[:]
        
    return centroids, cluster_assignment

In [75]:
import matplotlib.pyplot as plt

def plot_heterogeneity(heterogeneity, k):
    plt.figure(figsize=(7,4))
    plt.plot(heterogeneity, linewidth=4)
    plt.xlabel('# Iterations')
    plt.ylabel('Heterogeneity')
    plt.title('Heterogeneity of clustering over time, K={0:d}'.format(k))
    plt.rcParams.update({'font.size': 16})
    plt.tight_layout()

Let's do a first run at clustering, with k = 5


In [76]:
k = 5
heterogeneity = []
initial_centroids = get_initial_centroids(tfidf, k, seed=0)
centroids, cluster_assignment = kmeans(tfidf, k, initial_centroids, maxiter=400,
                                       record_heterogeneity=heterogeneity, verbose=True)


0
1
    44717 elements changed their cluster assignment.
2
    24885 elements changed their cluster assignment.
3
    13937 elements changed their cluster assignment.
4
     7539 elements changed their cluster assignment.
5
     5148 elements changed their cluster assignment.
6
     3472 elements changed their cluster assignment.
7
     2424 elements changed their cluster assignment.
8
     1717 elements changed their cluster assignment.
9
     1335 elements changed their cluster assignment.
10
     1116 elements changed their cluster assignment.
11
      959 elements changed their cluster assignment.
12
      894 elements changed their cluster assignment.
13
      801 elements changed their cluster assignment.
14
      748 elements changed their cluster assignment.
15
      602 elements changed their cluster assignment.
16
      436 elements changed their cluster assignment.
17
      372 elements changed their cluster assignment.
18
      302 elements changed their cluster assignment.
19
      278 elements changed their cluster assignment.
20
      291 elements changed their cluster assignment.
21
      280 elements changed their cluster assignment.
22
      301 elements changed their cluster assignment.
23
      336 elements changed their cluster assignment.
24
      499 elements changed their cluster assignment.
25
      710 elements changed their cluster assignment.
26
      980 elements changed their cluster assignment.
27
     1349 elements changed their cluster assignment.
28
     1795 elements changed their cluster assignment.
29
     2564 elements changed their cluster assignment.
30
     2345 elements changed their cluster assignment.
31
     1804 elements changed their cluster assignment.
32
     1416 elements changed their cluster assignment.
33
     1114 elements changed their cluster assignment.
34
      857 elements changed their cluster assignment.
35
      690 elements changed their cluster assignment.
36
      610 elements changed their cluster assignment.
37
      529 elements changed their cluster assignment.
38
      500 elements changed their cluster assignment.
39
      425 elements changed their cluster assignment.
40
      384 elements changed their cluster assignment.
41
      333 elements changed their cluster assignment.
42
      300 elements changed their cluster assignment.
43
      239 elements changed their cluster assignment.
44
      223 elements changed their cluster assignment.
45
      199 elements changed their cluster assignment.
46
      157 elements changed their cluster assignment.
47
      146 elements changed their cluster assignment.
48
      141 elements changed their cluster assignment.
49
      120 elements changed their cluster assignment.
50
       98 elements changed their cluster assignment.
51
       79 elements changed their cluster assignment.
52
       52 elements changed their cluster assignment.
53
       42 elements changed their cluster assignment.
54
       33 elements changed their cluster assignment.
55
       23 elements changed their cluster assignment.
56
       23 elements changed their cluster assignment.
57
       19 elements changed their cluster assignment.
58
       15 elements changed their cluster assignment.
59
       11 elements changed their cluster assignment.
60
       14 elements changed their cluster assignment.
61
       16 elements changed their cluster assignment.
62
       14 elements changed their cluster assignment.
63
       13 elements changed their cluster assignment.
64
       10 elements changed their cluster assignment.
65
        9 elements changed their cluster assignment.
66
        9 elements changed their cluster assignment.
67
       15 elements changed their cluster assignment.
68
       11 elements changed their cluster assignment.
69
        5 elements changed their cluster assignment.
70
        4 elements changed their cluster assignment.
71
        5 elements changed their cluster assignment.
72
        6 elements changed their cluster assignment.
73
        4 elements changed their cluster assignment.
74
        4 elements changed their cluster assignment.
75
        4 elements changed their cluster assignment.
76
        4 elements changed their cluster assignment.
77
        6 elements changed their cluster assignment.
78
        4 elements changed their cluster assignment.
79
        2 elements changed their cluster assignment.
80
        3 elements changed their cluster assignment.
81
        3 elements changed their cluster assignment.
82
        2 elements changed their cluster assignment.
83
        2 elements changed their cluster assignment.
84
        4 elements changed their cluster assignment.
85
        3 elements changed their cluster assignment.
86
        5 elements changed their cluster assignment.
87
        5 elements changed their cluster assignment.
88
        5 elements changed their cluster assignment.
89
        1 elements changed their cluster assignment.
90
        2 elements changed their cluster assignment.
91

In [77]:
%matplotlib inline
plot_heterogeneity(heterogeneity, k)



In [78]:
centroids


Out[78]:
array([[  0.00000000e+00,   1.05469622e-05,   3.71708150e-06, ...,
          0.00000000e+00,   0.00000000e+00,   0.00000000e+00],
       [  1.91948727e-05,   1.52130550e-05,   2.02610389e-05, ...,
          2.64351238e-07,   7.08147156e-03,   2.72742227e-05],
       [  5.60561232e-06,   3.63359293e-06,   6.64154034e-06, ...,
          0.00000000e+00,   0.00000000e+00,   0.00000000e+00],
       [  0.00000000e+00,   0.00000000e+00,   0.00000000e+00, ...,
          0.00000000e+00,   0.00000000e+00,   0.00000000e+00],
       [  3.72883925e-05,   7.23459019e-05,   4.63544071e-05, ...,
          0.00000000e+00,   0.00000000e+00,   0.00000000e+00]])

In [79]:
cluster_assignment


Out[79]:
array([4, 4, 4, ..., 4, 4, 4], dtype=int64)

In [59]:
documents[2]


Out[59]:
' Publicacao \n\n((TITULO))TÍTULO DE NOMEAÇÃO , DE  DE FEVEREIRO DE \n((TEXTO))FERNANDO HADDAD, Prefeito do Município de São Paulo, usando das atribuições que lhe são conferidas por lei,\nRESOLVE:\nNomear o senhor IVES CAMPOS LAZARINI, RF .., para exercer o cargo de Subprefeito, símbolo SBP, da Subprefeitura Butantã, constante das Leis ./ e ./.\nPREFEITURA DO MUNICÍPIO DE SÃO PAULO, aos\xa0\xa0de fevereiro de , ° da fundação de São Paulo. \nFERNANDO HADDAD, Prefeito\n\n((TITULO))TÍTULO DE NOMEAÇÃO , DE  DE FEVEREIRO DE \n((TEXTO))FERNANDO HADDAD, Prefeito do Município de São Paulo, usando das atribuições que lhe são conferidas por lei,\nRESOLVE:\nNomear o senhor RICARDO BRANDÃO FIGUEIREDO, RG ..--SSP/SP, para exercer o cargo de Presidente, símbolo PRE, da Autoridade Municipal de Limpeza Urbana \x96 AMLURB, vinculada à Secretaria Municipal de Serviços.\nPREFEITURA DO MUNICÍPIO DE SÃO PAULO, aos\xa0\xa0de fevereiro de , ° da fundação de São Paulo. \nFERNANDO HADDAD, Prefeito\n\n((TITULO))TÍTULO DE NOMEAÇÃO , DE  DE FEVEREIRO DE \n((TEXTO))FERNANDO HADDAD, Prefeito do Município de São Paulo, usando das atribuições que lhe são conferidas por lei,\nRESOLVE:\nNomear o senhor FABIO MANZINI CAMARGO, RG ..--SSP/SP, para exercer o cargo de Chefe de Gabinete, símbolo CHG, do Hospital do Servidor Público Municipal, vinculado à Secretaria Municipal da Saúde, constante das Leis ./ e ./.\nPREFEITURA DO MUNICÍPIO DE SÃO PAULO, aos\xa0\xa0de fevereiro de , ° da fundação de São Paulo. \nFERNANDO HADDAD, Prefeito\n\n\n'

In [ ]: